<?php
// +----------------------------------------------------------------------
// | zhanshop-device / Container.php    [ 2024/1/17 下午5:14 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2024 zhangqiquan All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhangqiquan <768617998@qq.com>
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace extend\docker;

use zhanshop\App;

class Container extends EngineApi
{
    /**
     * 获取容器列表
     * @return array
     * @throws \Exception
     */
    public function getList()
    {
        $containers = $this->request("/containers/json?all=1");
        $containers['body'] = strtolower($containers['body'] ?? "");
        $containers = json_decode($containers['body'], true);
        return $containers ?? [];
    }

    /**
     * 停止容器
     * @param string $id
     * @return array
     */
    public function stop(string $id)
    {
        $this->request("/containers/{$id}/stop", "POST");
        return [];
    }

    /**
     * 杀掉容器
     * @param string $id
     * @return array
     * @throws \Exception
     */
    public function kill(string $id)
    {
        $this->request("/containers/{$id}/kill", "PUT");
        return [];
    }
    /**
     * 启动容器
     * @param string $id
     * @return array
     * @throws \Exception
     */
    public function start(string $id)
    {
        $this->request("/containers/{$id}/start", "POST");
        return [];
    }
    /**
     * 重启容器
     * @param string $id
     * @return array
     * @throws \Exception
     */
    public function restart(string $id)
    {
        $this->request("/containers/{$id}/restart", "POST");
        return [];
    }
    /**
     * 暂停容器
     * @param string $id
     * @return array
     * @throws \Exception
     */
    public function pause(string $id)
    {
        $this->request("/containers/{$id}/pause", "POST");
        return [];
    }
    /**
     * 删除容器
     * @param string $id
     * @return array
     * @throws \Exception
     */
    public function delete(string $id)
    {
        $this->request("/containers/{$id}", "DELETE");
        return [];
    }

    /**
     * @param string $name
     * @param string $image
     * @param string $cmd
     * @param array $env
     * @param array $ports
     * @param array $volumes
     * @param $network
     * @return mixed
     * @throws \Exception
     */
    /*$d = new Container();
        $env = [
            "APP_ENV" => 'dev',
        ];
        $port = [
            '9201:9201/tcp'
        ];
        $volumes = [
            "3333:/var/www",
            "4444:/data/wwwroot",
        ];
        $ret = $d->create("test", "php:8.3.2-cli", "sleep 9999", $env, $port, $volumes);*/
    public function create(string $name, string $image, string $description, string $version, string $cmd, array $env = [], array $ports = [], array $volumes = [], int $cpuShares = 0, int $memory = 0, int $enEnableGPU = 0, $hostConf = [], $network = 'docker_zhanshop-network')
    {
        try {
            (new Network())->create($network);
        }catch (\Throwable $e){}
        $exposedPorts = [];
        $hostConfig = [
            "NetworkMode" => $network,
            "RestartPolicy" => [
                "Name" => "always",
                "MaximumRetryCount" => 0
            ],
            'Ulimits' => [
                [
                    'Name' => 'nproc',
                    'Hard' => 1024000,
                    'Soft' => 1024000,
                ],
                [
                    'Name' => 'nofile',
                    'Hard' => 1024000,
                    'Soft' => 1024000,
                ],
            ],
        ];
        if($enEnableGPU){
            $hostConfig['HostConfig'] = [
                'Runtime' => 'nvidia'
            ];
        }
        if($cpuShares) $hostConfig['CpuShares'] = $cpuShares;
        if($memory){
            if($memory < 104857600) $memory = 104857600; // 最小分配100M内存
            $hostConfig['Memory'] = $memory;
        }

        if($volumes['volumes']){
            $hostConfig['Binds'] = [];
            foreach ($volumes['volumes'] as $v){
                $hostConfig['Binds'][] = $v;
            }
        }

        if($volumes['mounts']){
            $hostConfig['Mounts'] = [];
            foreach ($volumes['mounts'] as $v){
                $hostConfig['Mounts'][] = $v;
            }
        }

        if($ports){
            $hostConfig['PortBindings'] = [];
            foreach ($ports as $v){
                if(strpos($v, ":") == false || strpos($v, ":") == false) App::error()->setError("端口参数参考：6201:6201/tcp", 403);
                $arr = explode(":", $v);
                $protocol = explode('/', $arr[1]);
                $exposedPorts[$protocol[0].'/'.$protocol[1]] = '##@##';
                $hostConfig['PortBindings'][$protocol[0].'/'.$protocol[1]] = [
                    [
                        "HostIp" => "",
					    "HostPort" => $arr[0]
                    ]
                ];
            }
        }

        $envs = [];
        foreach ($env as $k => $v){
            $envs[] = $k.'='.$v;
        }
        if($cmd){
            $cmd = explode(' ', $cmd);
        }else{
            $cmd = [];
        }
        if($hostConf){
            foreach ($hostConf as $k => $v){
                $hostConfig[$k] = $v;
            }
        }
        $postData = [
            'Env' => $envs,
            'Hostname' => $name,
            'Domainname' => $name,
            'Cmd' => $cmd,
            'Image' => $image,
            'ExposedPorts' => $exposedPorts,
            'HostConfig' => $hostConfig,
            'Labels' => [
                "description" => $description,
			    "version" => $version
            ]
        ];
        $postData = json_encode($postData);
        $postData = str_replace('"##@##"', '{}', $postData);
        $postData = str_replace('"ExposedPorts":[]', '"ExposedPorts":{}', $postData);
        $container = $this->setHeader('Content-Type', 'application/json')->request("/containers/create?name=".$name, "POST", $postData);

        if(strpos($container["body"], '"Id":"') === false){
            App::error()->setError($container['body']);
        }
        $container['body'] = strtolower($container['body'] ?? "");
        $container = json_decode($container['body'], true);
        return $container ?? [];
    }

    public function update()
    {

    }

    /**
     * 重命名
     * @param string $id
     * @param string $name
     * @return mixed
     * @throws \Exception
     */
    public function rename(string $id, string $name)
    {
        return $this->request("/containers/".$id."/rename?name=".$name, "POST");
    }

    /**
     * 获取镜像详情
     * @param string $id
     * @return mixed
     * @throws \Exception
     */
    public function detail(string $id)
    {
        $container = $this->request("/containers/".$id.'/json');
        $container['body'] = strtolower($container['body'] ?? "");
        $container = json_decode($container['body'], true);
        return $container ?? [];
    }

    /**
     * 列出容器内的进程
     * @param string $id
     * @return mixed
     * @throws \Exception
     */
    public function top(string $id)
    {
        $container = $this->request("/containers/".$id.'/top');
        $container['body'] = strtolower($container['body'] ?? "");
        $container = json_decode($container['body'], true);
        return $container ?? [];
    }

    /**
     * 获取容器内的日志
     * @param string $id
     * @return mixed
     * @throws \Exception
     */
    public function logs(string $id)
    {
        $container = $this->request("/containers/".$id.'/logs');
        $container['body'] = strtolower($container['body'] ?? "");
        $container = json_decode($container['body'], true);
        return $container ?? [];
    }

    /**
     * 获取容器资源使用统计
     * @param string $id
     * @return mixed
     * @throws \Exception
     */
    public function stats(string $id)
    {
        $statsData = [];
        $this->request("/containers/".$id.'/stats', "GET", [], function ($client, $data) use (&$statsData){
            $json = json_decode($data, true);
            if($json){
                $statsData = $json;
                $client->close();
            }
        });
        return $statsData;
    }

    /**
     * 在正在运行的容器中运行命令
     * @param string $id
     * @param string $cmd
     * @param array $param
     * @return void
     */
    public function exec(string $id, string $cmd, array $param = [])
    {
        $param['Cmd'] = explode(' ', $cmd);
        $execData = [];
        $this->request("/containers/".$id.'/exec', "POST", $param, function ($client, $data) use (&$execData){
            $data = json_decode($data, true);
            $client->close();
            if($data == false || isset($data['Id']) == false){
                App::error()->setError("运行命令失败", 417);
            }
            $this->request('/exec/'.$data['Id'].'/start', "POST", ['Detach' => false, 'Tty' => true], function ($client, $data) use (&$execData){
                $execData = $data;
                $client->close();
            });
        });
        return $execData;
    }
}